/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */
import {
	createConnection, TextDocuments, Diagnostic, DiagnosticSeverity, ProposedFeatures,
	InitializeParams, DidChangeConfigurationNotification, CompletionItem, CompletionItemKind,
	TextDocumentPositionParams, TextDocumentSyncKind, InitializeResult, DocumentDiagnosticReportKind,
	type DocumentDiagnosticReport, Location, Position, Range, DefinitionParams, Definition,
	DocumentFormattingParams, TextEdit,
	Hover,
	MarkupKind,
	ReferenceParams
} from 'vscode-languageserver/node';
import { FormattingOptions } from 'vscode-languageclient';

import { TextDocument } from 'vscode-languageserver-textdocument';

import { ParseTree } from 'antlr4ts/tree/ParseTree';
import { TerminalNode } from 'antlr4ts/tree/TerminalNode';

import * as ArkcubeScriptParser from './gen/ArkcubeScriptParser';
import * as ArkcubeScriptLexer from './gen/ArkcubeScriptLexer';

import * as AntlrUtils from './utils/AntlrUtils';
import { DocumentManager } from './utils/DocumentManager';
import { ArkcubeScriptFormater } from './utils/ArkcubeScriptFormater';
import { PositionAwareListener } from './utils/PositionAwareListener';
import { ArkcubeScriptValidator } from './utils/ArkcubeScriptValidator';



// Create a connection for the server, using Node's IPC as a transport.
// Also include all preview / proposed LSP features.
const connection = createConnection(ProposedFeatures.all);

// Create a simple text document manager.
const documents = new TextDocuments(TextDocument);

const documentManager = new DocumentManager();

let hasConfigurationCapability = false;
let hasWorkspaceFolderCapability = false;
let hasDiagnosticRelatedInformationCapability = false;

connection.onInitialize((params: InitializeParams) => {
	console.log('server onInitialize ...')

	const capabilities = params.capabilities;

	// Does the client support the `workspace/configuration` request?
	// If not, we fall back using global settings.
	hasConfigurationCapability = !!(
		capabilities.workspace && !!capabilities.workspace.configuration
	);
	hasWorkspaceFolderCapability = !!(
		capabilities.workspace && !!capabilities.workspace.workspaceFolders
	);
	hasDiagnosticRelatedInformationCapability = !!(
		capabilities.textDocument &&
		capabilities.textDocument.publishDiagnostics &&
		capabilities.textDocument.publishDiagnostics.relatedInformation
	);

	const result: InitializeResult = {
		capabilities: {
			textDocumentSync: TextDocumentSyncKind.Incremental,
			// Tell the client that this server supports code completion.
			completionProvider: {
				resolveProvider: true, // 是否支持 onCompletionResolve
				triggerCharacters: [".", "("], // 触发补全的字符
			},
			definitionProvider: true,
			diagnosticProvider: {
				interFileDependencies: true,
				workspaceDiagnostics: false
			},
			documentFormattingProvider: true,
			hoverProvider: true,
		}
	};
	if (hasWorkspaceFolderCapability) {
		result.capabilities.workspace = {
			workspaceFolders: {
				supported: true
			}
		};
	}
	return result;
});

connection.onInitialized(() => {
	console.log('server onInitialized ...')
	if (hasConfigurationCapability) {
		// Register for all configuration changes.
		connection.client.register(DidChangeConfigurationNotification.type, undefined);
	}
	if (hasWorkspaceFolderCapability) {
		connection.workspace.onDidChangeWorkspaceFolders(_event => {
			connection.console.log('Workspace folder change event received.');
		});
	}
});

// The example settings
interface ExampleSettings {
	maxNumberOfProblems: number;
}

// The global settings, used when the `workspace/configuration` request is not supported by the client.
// Please note that this is not the case when using this server with the client provided in this example
// but could happen with other clients.
const defaultSettings: ExampleSettings = { maxNumberOfProblems: 1000 };
let globalSettings: ExampleSettings = defaultSettings;

// Cache the settings of all open documents
const documentSettings = new Map<string, Thenable<ExampleSettings>>();

connection.onDidChangeConfiguration(change => {
	if (hasConfigurationCapability) {
		// Reset all cached document settings
		documentSettings.clear();
	} else {
		globalSettings = (
			(change.settings.arkcubeScriptLanguageServer || defaultSettings)
		);
	}
	// Refresh the diagnostics since the `maxNumberOfProblems` could have changed.
	// We could optimize things here and re-fetch the setting first can compare it
	// to the existing setting, but this is out of scope for this example.
	connection.languages.diagnostics.refresh();
});

function getDocumentSettings(resource: string): Thenable<ExampleSettings> {
	if (!hasConfigurationCapability) {
		return Promise.resolve(globalSettings);
	}
	let result = documentSettings.get(resource);
	if (!result) {
		result = connection.workspace.getConfiguration({
			scopeUri: resource,
			section: 'arkcubeScriptLanguageServer'
		});
		documentSettings.set(resource, result);
	}
	return result;
}

// Only keep settings for open documents
documents.onDidClose(e => {
	console.log('documents.onDidChangeContent: ', e.document.uri);
	documentSettings.delete(e.document.uri);
});


// 这是注册一个事件监听器，用于处理来自客户端的诊断请求（textDocument/diagnostic 请求）。
// 这个请求通常由编辑器在需要获取文档诊断信息时发起，比如：
// - 用户切换到另一个文件标签页
// - 编辑器希望显示错误/警告信息
// - 用户手动触发了诊断更新
// 注意：languages.diagnostics.on 是 LSP v3.17+ 中引入的新特性（Document Diagnostic Request），
// 它不同于传统的服务器主动推送诊断的方式（即 sendDiagnostics）。
connection.languages.diagnostics.on(async (params) => {
	const document = documents.get(params.textDocument.uri);
	if (document !== undefined) {
		return {
			kind: DocumentDiagnosticReportKind.Full,
			items: await validateTextDocument(document)
		} satisfies DocumentDiagnosticReport;
	} else {
		// We don't know the document. We can either try to read it from disk
		// or we don't report problems for it.
		return {
			kind: DocumentDiagnosticReportKind.Full,
			items: []
		} satisfies DocumentDiagnosticReport;
	}
});

// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
documents.onDidChangeContent(async change => {
	console.log('documents.onDidChangeContent: ', change.document.uri);
	documentManager.updateDocument(change.document);
});

async function validateTextDocument(textDocument: TextDocument): Promise<Diagnostic[]> {
	// In this simple example we get the settings for every validate run.
	const settings = await getDocumentSettings(textDocument.uri);
	let maxNumberOfProblems = settings.maxNumberOfProblems;

	return ArkcubeScriptValidator.validate(documentManager, textDocument.uri, maxNumberOfProblems);
}

connection.onDidChangeWatchedFiles(_change => {
	// Monitored files have change in VSCode
	connection.console.log('We received a file change event');
});

// This handler provides the initial list of the completion items.
connection.onCompletion((params: TextDocumentPositionParams): CompletionItem[] | null => {
	console.log('connection.onCompletion: ', params.textDocument.uri);
	const positionAst = documentManager.getPositionAst(params.textDocument.uri, params.position);
	if (!positionAst) {
		return null;
	}

	const posAstStr = positionAst.text;
	console.log('onCompletion positionAst: ', posAstStr);
	const suggestions: CompletionItem[] = [];

	suggestions.push({
		label: 'test label', // 补全列表的标识，以及实际补全的内容
		kind: CompletionItemKind.Function, // 补全类型，如 CompletionItemKind.Function, .Variable 等
		detail: 'test detail', // 补全提示
		documentation: 'test documentation' // 可选，详细描述
	});

	return suggestions;
});

// This handler resolves additional information for the item selected in
// the completion list.
connection.onCompletionResolve((item: CompletionItem): CompletionItem => {
	if (item.data === 1) {
		item.detail = 'TypeScript details';
		item.documentation = 'TypeScript documentation';
	} else if (item.data === 2) {
		item.detail = 'JavaScript details';
		item.documentation = 'JavaScript documentation';
	}
	return item;
}
);

connection.onDefinition((params: DefinitionParams): Definition | null => {
	const textDocument = documentManager.getOrCreateDocument(params.textDocument.uri);
	if (!textDocument) return null;

	const ast = documentManager.getOrCreateAST(params.textDocument.uri);
	if (!ast) return null;

	const position = params.position;
	const positionAst = PositionAwareListener.getPositionAst(position, ast);
	if (!positionAst) {
		return null;
	}

	// 查找定义位置
	const posAstStr = positionAst.text;
	if (positionAst instanceof TerminalNode) {
		let parent = positionAst.parent;
		if (parent instanceof ArkcubeScriptParser.FunctionCallContext) {
			if (AntlrUtils.isLibFunc(posAstStr)) {
				return null
			}
			if (parent.parent instanceof ArkcubeScriptParser.DotFunctionApplyContext) {
				const alias = parent.parent.Identifier().text;
				return getDotFunctionLocation(ast, alias, posAstStr, params.textDocument.uri);
			} else {
				return getFunctionLocation(ast, posAstStr, params.textDocument.uri);
			}

		}
	}

	return null;
});

function getDotFunctionLocation(ast: ArkcubeScriptParser.ProgramContext, alias: string, funcName: string, posUri: string): Location | null {
	const stats = ast.stat();
	for (let i = 0; i < stats.length; i++) {
		const stat = stats[i];
		if (stat instanceof ArkcubeScriptParser.ImportStatementContext) {
			const importStat = stat.importStat();
			if (importStat instanceof ArkcubeScriptParser.ModuleAliasContext) {
				if (alias === importStat._alias.text) {
					const scriptName = importStat._name.text?.slice(1, -1);
					const folderPath = posUri.substring(0, posUri.lastIndexOf('/') + 1);
					const relyUri = folderPath + scriptName + '.arks';
					const relyAst = documentManager.getOrCreateAST(relyUri);
					if (relyAst) {
						return getFunctionLocation(relyAst, funcName, relyUri);
					}
				}
			}
		}
	}
	return null;
}

function getFunctionLocation(ast: ArkcubeScriptParser.ProgramContext, funcName: string, posUri: string): Location | null {
	const stats = ast.stat();
	for (let i = 0; i < stats.length; i++) {
		const stat = stats[i];
		if (stat instanceof ArkcubeScriptParser.FunctionDeclStatementContext) {
			const id = stat.functionDecl().Identifier();
			if (id.text === funcName) {
				return {
					uri: posUri,
					range: getRange(id)
				};
			}
		}
	}
	return null;
}

function getRange(tree: TerminalNode): Range {
	return {
		start: {
			line: tree._symbol.line - 1,
			character: tree._symbol.charPositionInLine
		},
		end: {
			line: tree._symbol.line - 1,
			character: tree._symbol.charPositionInLine + tree.text.length
		}
	};
}

connection.onReferences((params: ReferenceParams): Location[] | null => {
	console.log('connection.onReferences:', params.textDocument.uri);
	
	const positionAst = documentManager.getPositionAst(params.textDocument.uri, params.position);
	return null;
});


connection.onDocumentFormatting((params: DocumentFormattingParams): TextEdit[] | null => {
	console.log('connection.onDocumentFormatting:', params.textDocument.uri);
	const textDocument = documents.get(params.textDocument.uri);
	if (!textDocument) return null;

	const ast = documentManager.getOrCreateAST(params.textDocument.uri);
	if (!ast) return null;

	const options: FormattingOptions = params.options;
	return ArkcubeScriptFormater.format(ast, options);
});

connection.onHover((params: TextDocumentPositionParams): Hover | null => {
	console.log('connection.onHover:', params.textDocument.uri);
	connection.console.log(`connection.onHover:${params.textDocument.uri}`);
	const positionAst = documentManager.getPositionAst(params.textDocument.uri, params.position);
	if (!positionAst) {
		return null;
	}

	const posAstStr = positionAst.text;
	if (positionAst instanceof TerminalNode) {
		let parent = positionAst.parent;
		if (parent instanceof ArkcubeScriptParser.FunctionCallContext) {
			return {
				contents: {
					kind: MarkupKind.Markdown,
					value: `${posAstStr}`
				},
				range: getRange(positionAst)
			};
		}
	}

	return null;
});

// Make the text document manager listen on the connection
// for open, change and close text document events
documents.listen(connection);

// Listen on the connection
connection.listen();
